home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 1.iso / toolbox / documents / audio / audio.apps / dev / interactive < prev    next >
Encoding:
Text File  |  1996-11-11  |  4.4 KB  |  95 lines

  1.  
  2. On Jun 14,  2:02pm, jon madison wrote:
  3. > i'm writing a sound app that wants to play more than
  4. > one sound at a time. (it's a drum machine, has different
  5. > pads.) i want the silly thing to know that if when i press
  6. > a button to go on & play the next sample, not sit & wait
  7. > til the queue has finished moving the samples to the audioport.
  8. > i can't check if this is so *before* i open the port...
  9. >
  10. > is this the time when i should follow the instructions on:
  11. >
  12. > A) Using non-blocking i/o
  13. > B) doing that sproc() stuff
  14. >
  15. > or all of the above?
  16.  
  17. Probably B, and some other things. Here's a general architecture that works
  18. well for low-latency interactive apps:
  19.  
  20. Have two processes, a UI process, and an audio process. The latter runs at
  21. high priority. It spends most of its time blocked so it doesn't eat the
  22. CPU. Use sproc() for your second process so the two share memory.
  23.  
  24. The audio process sits in a loop that looks like:
  25.     while(1) {
  26.     block until one of two things happens:
  27.         (a) we get an event to do something from the UI process
  28.         (b) the audio port needs more data
  29.     if (a) {
  30.         get the event
  31.         do what it says
  32.     }
  33.     else if (b) {
  34.         compute the next chunk of data to be sent out the port
  35.     }
  36.     }
  37.  
  38. The blocking is accomplished using select(), which means you need to
  39. get file descriptors associated with actions (a) and (b). For (a) the
  40. best way is a pollable semaphore. If you haven't played with these,
  41. they're quite cool. See the usnewsema and usnewpollsema man pages. (I'm
  42. assuming you know how semaphores work in general. If not,  they're
  43. pretty easy to learn,  and really useful).  You get a semaphore to
  44. share between processes. A pollable semaphore works like this: you
  45. initialize the semaphore to 0. The audio process gets a file descriptor
  46. for the semaphore,  upon which it can block. If the audio process does
  47. a uspsema() and it fails, the audio process can then block on select
  48. waiting for the semaphore to become ready. When the UI process does a
  49. usvsema(),  the audio process will wake up. So you have the UI process
  50. do the usvsema when it has something new for the audio process to do. A
  51. nice clean way to have one process wake another up.
  52.  
  53. For (b) you use ALgetfd and ALsetfillpoint.
  54.  
  55. The key to an *interactive* application is that you want low latency.
  56. What you said: you don't want to sit and wait for all the samples to
  57. go out the queue. When the user hits a button,  he/she wants to hear
  58. the sound right away.
  59.  
  60. The problem is, it's a queue. If you've already put stuff in it,  you
  61. have to wait. So here's how you do it.  You keep only a couple of
  62. milliseconds' worth of data in the queue at a time. This way,  any new
  63. data goes out almost immediately. Let's say you want to have a 20
  64. millisecond latency in your queue. You block until there is only 10
  65. milliseconds' worth of data in the queue (ALsetfillpoint sets the point
  66. at which you get unblocked),  then you put 10 milliseconds' worth of
  67. data in. This means your audio app wakes up every 10 milliseconds to
  68. put another 10ms worth of data in the queue. You are keeping the queue
  69. filled with a constant amount of data, instead of allowing it to fill up.
  70. This means your application is in control of the latency. If you want
  71. lower latency, you maintain less data in your queue. The down-side is
  72. that your app needs to be scheduled more often. Another little tip:
  73. if you maintain only a little data in your queue, you don't need a big
  74. queue; use ALsetqueuesize to shrink your queue so you don't waste system
  75. memory.
  76.  
  77. So the remaining tricks are how to generate the data itself. The coolest
  78. way to do this is to allow multiple simultaneous sounds. If a user hits
  79. a cymbal pad, it's got a long decay. You don't want it to cut it off when
  80. he/she hits some other pad. So now your little piece of code to generate
  81. 10ms worth of data has to remember which sounds are currently playing,
  82. and where each is in the sound. So you keep a little list. When your
  83. audio process gets an "event" from the UI to start playing a sound,  all
  84. it does is add that sound to the list, and note that the sound is at
  85. the beginning. Next time the audio process wakes up to fill the queue,
  86. it generates 10ms worth of data for ALL the sounds on the list, mixes
  87. the data together,  and advances time by 10ms for all the items on the
  88. list. Items are removed which reach the end of the sound.
  89.  
  90. Alternatively, you could use the MIDI library to generate MIDI to some
  91. internal software synthesizer or external synth.
  92.  
  93.     -Doug
  94.  
  95.